package service

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/net/ipv4"
	"io"
	"net"
	"onvif_server/api/system"
	"onvif_server/internal/errcode"
	"onvif_server/internal/repository"
	"onvif_server/internal/utils"
	"onvif_server/pkg/storage"
	"os"
	"path/filepath"
	"syscall"
	"time"
)

type SystemService interface {
	Info(ctx context.Context) (any, error)
	Reboot(ctx context.Context, uid int64, pwd string) error
	Upgrade(ctx context.Context) error
	Upload(ctx *gin.Context, fileField string) (res *system.UploadRes, err error)
	FileOutput(ctx context.Context, file string) (io.ReadCloser, string, error)
	Discovery(ctx context.Context) ([]any, error)
}

func NewSystemService(service *Service, userRepo repository.UserRepository) SystemService {
	return &systemService{
		Service:  service,
		userRepo: userRepo,
	}
}

type systemService struct {
	*Service
	userRepo repository.UserRepository
}

func (s *systemService) Info(ctx context.Context) (any, error) {
	res, err := utils.GetServerInfo()
	if err != nil {
		return nil, err
	}
	st, tt := utils.GetSysRunTime()
	return struct {
		utils.ServerInfo
		Running struct {
			StartTime string `json:"start_time"`
			RunTime   string `json:"run_time"`
		} `json:"running"`
	}{
		ServerInfo: res,
		Running: struct {
			StartTime string `json:"start_time"`
			RunTime   string `json:"run_time"`
		}{StartTime: st.Format(utils.FormatDatetime),
			RunTime: tt.String(),
		},
	}, nil
}

func (s *systemService) Reboot(ctx context.Context, uid int64, pwd string) (err error) {
	defer s.log.Info("reboot", zap.Time("time", time.Now()), zap.Error(err))
	m, err := s.userRepo.GetByID(ctx, uid)
	if err != nil {
		return err
	}
	if !m.IsSuperUser() {
		return errcode.ErrNeedSuper
	}
	if bcrypt.CompareHashAndPassword(m.Password, []byte(pwd)) != nil {
		return errcode.ErrInvalidPwd
	}
	exePath, err := os.Executable()
	if err != nil {
		return err
	}
	s.log.Info("syscall.Exec", zap.String("execPath", exePath), zap.Strings("os.Args", os.Args), zap.Strings("os.Environ", os.Environ()))
	// 使用 syscall.Exec 来替换当前进程
	go func() {
		time.Sleep(time.Second)
		err = syscall.Exec(exePath, os.Args, os.Environ())
		if err != nil {
			s.log.Error("syscall.Exec error", zap.Error(err))
		}
	}()
	return nil
}

func (s *systemService) Upgrade(_ctx context.Context) error {
	ctx, ok := _ctx.(*gin.Context)
	if !ok {
		return errcode.ErrBadRequest
	}
	err := ctx.Request.ParseMultipartForm(s.config.UploadMaxSize)
	if err != nil {
		return err
	}

	_, fh, err := ctx.Request.FormFile("file")
	if err != nil {
		return err
	}
	if fh.Header.Get("Context-Type") != "application/octet-stream" {
		return errcode.ErrBadRequest
	}
	return nil
}

func (s *systemService) Upload(ctx *gin.Context, fileField string) (res *system.UploadRes, err error) {
	err = ctx.Request.ParseMultipartForm(s.config.UploadMaxSize)
	if err != nil {
		return nil, err
	}
	_, fh, err := ctx.Request.FormFile(fileField)
	if err != nil {
		return nil, err
	}
	store, err := s.getStorage()
	if err != nil {
		return nil, err
	}
	fp, err := fh.Open()
	if err != nil {
		return nil, err
	}
	defer fp.Close()
	var fileName string
	ext := filepath.Ext(fh.Filename)
	if ext == "" {
		fileName = uuid.New().String()
	} else {
		fileName = uuid.New().String() + ext
	}
	if err = store.Put(fileName, fp); err != nil {
		return nil, err
	}
	return &system.UploadRes{
		Url:  store.Url(fileName),
		File: fh.Filename,
		Size: fh.Size,
	}, nil
}

func (s *systemService) FileOutput(ctx context.Context, file string) (io.ReadCloser, string, error) {
	var store storage.Storage
	store, err := s.getStorage()
	if err != nil {
		return nil, "", err
	}
	rc, err := store.Get(file)
	if err != nil {
		return nil, "", err
	}
	ct, _ := store.MimeType(file)
	return rc, ct, nil
}

// Discovery 组播查找盒子
func (s *systemService) Discovery(ctx context.Context) (res []any, err error) {
	c, err := net.ListenPacket("udp4", "0.0.0.0:0")
	if err != nil {
		return nil, err
	}
	defer c.Close()

	ifcs := s.getNetInterfaces()
	ifc, ok := ifcs.Interfaces[ifcs.Default]
	if !ok {
		return nil, errors.New("为获取到网卡信息")
	}

	p := ipv4.NewPacketConn(c)
	group := net.IPv4(239, 255, 255, 250)
	if err := p.JoinGroup(ifc.Ifc, &net.UDPAddr{IP: group}); err != nil {
		return nil, err
	}

	dst := &net.UDPAddr{IP: group, Port: 7659}
	data := []byte("find")
	for _, ifi := range []*net.Interface{ifc.Ifc} {
		if err = p.SetMulticastInterface(ifi); err != nil {
			return nil, err
		}
		p.SetMulticastTTL(2)
		if _, err = p.WriteTo(data, nil, dst); err != nil {
			return nil, err
		}
	}

	if err = p.SetReadDeadline(time.Now().Add(time.Second * 1)); err != nil {
		return nil, err
	}
	var result []any
	for {
		b := make([]byte, 8192)
		n, _, _, err := p.ReadFrom(b)
		if err != nil {
			if !errors.Is(err, os.ErrDeadlineExceeded) {
				return nil, err
			}
			break
		}
		_res := make(map[string]any)
		_err := json.Unmarshal(b[0:n], &_res)
		if _err == nil {
			result = append(result, _res)
		}
	}
	return result, nil
}
